package pl.wroc.pwr.docs.repos.service.impl;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;

import org.apache.commons.io.IOUtils;
import org.apache.lucene.index.TermFreqVector;
import org.apache.tika.Tika;
import org.apache.tika.exception.TikaException;
import org.apache.tika.metadata.DublinCore;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.parser.AutoDetectParser;
import org.hibernate.search.jpa.FullTextQuery;
import org.hibernate.search.query.facet.Facet;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.xml.sax.helpers.DefaultHandler;

import com.google.common.base.Functions;
import com.google.common.collect.Ordering;
import pl.wroc.pwr.docs.repos.dao.DocumentDao;
import pl.wroc.pwr.docs.repos.domain.Document;
import pl.wroc.pwr.docs.repos.domain.Document.FORMAT;
import pl.wroc.pwr.docs.repos.service.DocumentService;

//TODO dodać hash dokumentu, zeby nie dodawać duplikatow

@Service("documentService")
public class DocumentServiceImpl implements DocumentService {
	
	@Autowired
	private DocumentDao documentDao;

	private static Tika tika = new Tika();
	//TODO sprawdzić czy to jest wyczerpujaca lista
	private static HashMap<String, FORMAT> TIKA_FORMAT_MAPPING;
	static {
		TIKA_FORMAT_MAPPING = new HashMap<String, Document.FORMAT>();
		TIKA_FORMAT_MAPPING.put("application/msword", FORMAT.DOC);
		TIKA_FORMAT_MAPPING.put("application/x-tika-msoffice", FORMAT.DOC);
		TIKA_FORMAT_MAPPING.put("application/vnd.openxmlformats-officedocument.wordprocessingml.document", FORMAT.DOCX);
		TIKA_FORMAT_MAPPING.put("application/x-tika-ooxml", FORMAT.DOCX);
		TIKA_FORMAT_MAPPING.put("application/vnd.oasis.opendocument.text", FORMAT.ODT);
		TIKA_FORMAT_MAPPING.put("application/zip", FORMAT.ODT);
		TIKA_FORMAT_MAPPING.put("application/pdf", FORMAT.PDF);
		TIKA_FORMAT_MAPPING.put("application/rtf",FORMAT.RTF);
		TIKA_FORMAT_MAPPING.put("text/plain", FORMAT.TXT);
	}
	
	private boolean isDocument(InputStream in) throws IOException {
		String tikaFormat = tika.detect(in);
		return TIKA_FORMAT_MAPPING.containsKey(tikaFormat);
	}
	
	private FORMAT extractFormat(InputStream in) throws IOException {
		String tikaFormat = tika.detect(in);
		FORMAT format = TIKA_FORMAT_MAPPING.get(tikaFormat);
		return format != null ? format : FORMAT.Inny;
	}
	
	private static String extractContent(InputStream in) throws IOException, TikaException {
		return extractContent(in,-1);
	}
	
	private static String extractContent(InputStream in, int maxStringLength) throws IOException, TikaException {
		tika.setMaxStringLength(maxStringLength);
		return tika.parseToString(in);
	}
	
	public void addMetadata(Document doc, Metadata metadata) {
		Date created = metadata.getDate(DublinCore.CREATED);
		String author = metadata.get(DublinCore.CREATOR);
		String title = metadata.get(DublinCore.TITLE);
		
		if(created != null)
			doc.setCreated(new java.sql.Date(created.getTime()));
		if(author != null)
			doc.equals(author);
		if(title != null)
			doc.setTitle(title);
	}
	
	private static Metadata extractMetadata(InputStream in) throws Exception {
		Metadata metadata = new Metadata();
		AutoDetectParser parser = new AutoDetectParser();
		parser.parse(in, new DefaultHandler(), metadata);
		return metadata;
	}
	
	@Override
	public void add(String filename, InputStream in) throws Exception {
		byte[] bytes = IOUtils.toByteArray(in);
		Document doc = new Document();
		doc.setBinaryData(bytes);
		doc.setFilename(filename);
		ByteArrayInputStream b = new ByteArrayInputStream(bytes);
		doc.setIndexedContent(extractContent(b, Document.MAX_CONTENT_LENGTH));
		b.reset();
		Metadata metadata = extractMetadata(b);
		addMetadata(doc, metadata);
		b.reset();
		doc.setFormat(extractFormat(b));
		doc.setAdded(new java.sql.Date((new Date()).getTime()));
		documentDao.save(doc);
	}
		
	@Override
	public void save(Document doc) {
		documentDao.save(doc);
	}
	
	@Override
	public void delete(int id) {
		documentDao.delete(id);
	}
	
	@Override
	public Document getDocument(int id) {
		return documentDao.findOne(id);
	}
	
	@Override
	public List<Document> getDocuments() {
		return (List<Document>) documentDao.findAll();
	}

	@Override
	public Page<Document> getDocumentsPage(Integer pageNumber) {
		PageRequest request = new PageRequest(pageNumber - 1, 10, Sort.Direction.DESC, "id");
		return documentDao.findAll(request);
	 }
	
	@Override
	public FullTextQuery buildQuery (String content){
		return documentDao.buildQuery(content);
	}
	
	public void applyFacets(FullTextQuery fullTextQuery, List<Facet> facets) {
		documentDao.applyFacets(fullTextQuery, facets);
	}
	
	public void disableFacets(FullTextQuery fullTextQuery) {
		documentDao.disableFacets(fullTextQuery);
	}
	
	@Override
	public Page<Document> search(Integer pageNumber, FullTextQuery fullTextQuery) {
		PageRequest request = new PageRequest(pageNumber - 1, 10);
		return documentDao.search(request, fullTextQuery);
	}
	
	@Override
	public List<Facet> getFacets(FullTextQuery fullTextQuery) {
		return documentDao.getFacets(fullTextQuery);
	}

	@Override
	public List<String> getTermsSortedByFrequency(int id) throws IOException {
		final int MAX_RETURNED_TERMS = 50;
		TermFreqVector tfv = documentDao.getTermFreqVector(id);
		
		if(tfv != null){
			String[] terms = tfv.getTerms();
			int[] freqs = tfv.getTermFrequencies();
			HashMap<String, Integer> map = new HashMap<String, Integer>();
		
			for(int i = 0; i < terms.length; i++){
				map.put(terms[i], freqs[i]);
			}
		
			List<String> sorted = Ordering.natural()
			                              .onResultOf(Functions.forMap(map))
			                              .reverse()
			                              .immutableSortedCopy(map.keySet());
		
			return sorted.subList(0, sorted.size() > MAX_RETURNED_TERMS 
			                         ? MAX_RETURNED_TERMS : sorted.size()
			                         );
		}
		else {
			return new ArrayList<String>();
		}
	}

	@Override
	public FullTextQuery filtrQuery(FullTextQuery fullTextQuery, List<String> formats) {
		
		return documentDao.filtrQuery(fullTextQuery, formats);
	}

	@Override
	public FullTextQuery resetQuery(FullTextQuery fullTextQuery) {
		
		return documentDao.resetQuery(fullTextQuery);
	}
}